package uk.ac.warwick.dcs.csufbo;

import java.util.ArrayList;
import java.util.Random;
import java.util.HashMap;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;

import com.micropraxis.gajit.Chrom;

public class LSys
{
	private ArrayList<Rule> ruleList;
	private ArrayList<Integer> axiom;
	
  
  /*
   * Constructor. Creates random rules and axiom.
   */
	public LSys(int numOfRules, int axiLen)
	{
		Random randomGenerator = new Random();
		ruleList = new ArrayList<Rule>(numOfRules);

		for (int i = 0; i < numOfRules; i++)
		{
      // Add a new random rule with premise of length 2
      // and conclusion of length 3.
      
			ruleList.add(i, new Rule(2, 3));                                 
			System.out.println(ruleList.get(i).getStrPremise() + " -> "
					+ ruleList.get(i).getStrConclusion());
		}

		axiom = new ArrayList<Integer>(axiLen);

		System.out.print("Axiom: ");

		for (int j = 0; j < axiLen; j++)
		{
      //Generate a new random number between 0 and 11
      //and append it to the axiom.
      
			axiom.add(j, randomGenerator.nextInt(12));
			System.out.print(axiom.get(j) + ",");
		}

		System.out.println();
	}

  
  /*
   * Constructor given ruleList and axiom.
   */
	public LSys(ArrayList<Rule> ruleList, ArrayList<Integer> axiom)
	{
		ruleList = this.ruleList;
		axiom = this.axiom;
	}

  /*
   * Constructor which builds the L-System structure
   * from the chromosome structure.
   */
	public LSys(Chrom lChrom)
	{
		Rule tempRule;
		ruleList = new ArrayList<Rule>((lChrom.size() - 1) / 2);

		if (lChrom.size() > 0)
		{
      //Set first gene as axiom
			axiom = (ArrayList<Integer>) lChrom.get(0);

			for (int i = 1; i < lChrom.size() - 1; i = i + 2)
			{
        //For each pair of odd and even element, create a
        //new rule and add to the rule list.
        
				tempRule = new Rule(lChrom.get(i), lChrom.get(i + 1));
				ruleList.add(tempRule);
			}
		}
	}

  
  /*
   * Method to process the L-System and return the output string.
   * Uses Karp-Rabin for pattern matching.
   */
	public ArrayList<Integer> getOutputString(int maxLength, int maxPasses)
	{
		ArrayList<Integer> outputString, tempString;
		HashMap<Integer, Rule> hsubs;
		Integer hs;
		Integer numPasses = 0;

		outputString = new ArrayList<Integer>(axiom);

		do
		{
			hsubs = new HashMap<Integer, Rule>(ruleList.size());
      
      //For each rule...
			for (int j = 0; j < ruleList.size(); j++)
        //Calculate the hash value of the premise and put the rule into a hash table
				hsubs.put(hash(ruleList.get(j).getPremise()), ruleList.get(j));

			tempString = new ArrayList<Integer>(2);

			int z = 0;
			while (z < outputString.size() - 2)
			{

				tempString.add(outputString.get(z));
				tempString.add(outputString.get(z + 1)); // DODGY. Assumes rules only have premises of length 2. This was done to speed up pattern match.

        //Calculate hash for substring of axiom
				hs = hash(tempString);

        //If substring hash matches rule premise, replaces with conclusion.
				if (hsubs.containsKey(hs))
				{
					// System.out.println("Found Key. Checking if " +
					// tempString.toString() + " = " +
					// hsubs.get(hs).getPremise().toString());
					if (tempString.equals(hsubs.get(hs).getPremise()))
					{
						outputString.remove(z + 1);
						outputString.remove(z); // DODGY! Assumes we're still only using substrings of length 2.
						outputString.addAll(z, hsubs.get(hs).getConclusion());
						// System.out.println("Performed replacement");
						z = z + 3;
					}
					else
					{
						z = z + 2;
					}

					// System.out.println("New Axiom:" +
					// outputString.toString());
				}
				else
				{
					z = z + 2;
				}

				tempString.clear();
			}

			numPasses++;

		} while ((outputString.size() < maxLength) && numPasses < maxPasses);

		if (outputString.size() > maxLength)
		{
			for (int l = outputString.size() - 1; outputString.size() > maxLength; l--)
			{
				outputString.remove(l);
			}
		}

		// System.out.println("Output String:" + outputString);

		return outputString;
	}

  //Method to return L-System as an Array List of Array Lists. Similar to Chrom structure.
	public ArrayList<ArrayList<Integer>> getArrayList()
	{
		ArrayList<ArrayList<Integer>> tempList = new ArrayList<ArrayList<Integer>>();

		tempList.add(axiom);

		for (int i = 0; i < ruleList.size(); i++)
		{
			tempList.add(ruleList.get(i).getPremise());
			tempList.add(ruleList.get(i).getConclusion());
		}

		return tempList;
	}

  //Method to return a MIDI sequence generated by the L-System
	public Sequence toSequence(int root, int maxLength, int numOfPasses)
	{
		Sequence midSeq;
		ArrayList<Integer> returnedNoteList;


		try
		{
      //Set sequence to divide by 'Pulses per quarter note' with a resolution of 16.
			midSeq = new Sequence(Sequence.PPQ, 16, 1);
		}
		catch (InvalidMidiDataException e1)
		{
			// TODO Auto-generated catch block
			e1.printStackTrace();
			midSeq = null;
		}
		
		Track track = midSeq.createTrack();
		
    //Use getoutputstring method to return array list of integers
		returnedNoteList = getOutputString(maxLength, numOfPasses);

    //Create midi events from array list of integers.
		for (int i = 0; i < returnedNoteList.size(); i++)
		{
			try
			{
		        ShortMessage on = new ShortMessage( );
		        on.setMessage(ShortMessage.NOTE_ON,0,  root + returnedNoteList.get(i), 100);
		        ShortMessage off = new ShortMessage( );
		        off.setMessage(ShortMessage.NOTE_OFF, 0, root + returnedNoteList.get(i), 100);
		        track.add(new MidiEvent(on, i * 8));
		        track.add(new MidiEvent(off, (i * 8) + 8));

				
//				message.setMessage(ShortMessage.NOTE_ON, root
//						+ returnedNoteList.get(i), 0);
//				notething = new MidiEvent(message, i);
			}
			catch (InvalidMidiDataException e)
			{
				e.printStackTrace();
			}

//			midSeq.getTracks()[0].add(notething);
		}

		System.out.println(midSeq.getTracks()[0]);

		return midSeq;
	}

  //Hash function for output string pattern match (karp rabin)
	private Integer hash(ArrayList<Integer> noteString)
	{
		int base = 101;
		int result = 0;

		for (int i = 0; i < noteString.size(); i++)
		{
			result = result + (noteString.get(i) * (base ^ i));
		}

		return (Integer) result;
	}

}
